/*
 * User.cpp
 *
 *  Created on: Jun 10, 2011
 *      Author: Mario Hock
 */

#include "User.h"
#include "Flow_control.h"

//** FACTORIES **/   // TODO shared_from_this würde es auch tun...
UserPtr User::createUser(string name)
{
    UserPtr u(new User(name));
    u->give_self_reference(u);
    return u;
}

UserPtr User::createUser(const ariba::NodeID &id)
{
    UserPtr u(new User(id));
    u->give_self_reference(u);
    return u;
}


UserPtr User::createUser(ptree &pt)
{
    UserPtr u(new User(pt));
    u->give_self_reference(u);
    return u;
}

void User::give_self_reference(boost::shared_ptr<User> ptr)
{
    boost::weak_ptr<User> wptr(ptr);
    self_reference = wptr;
}

//** Constructors / Destructors **//
// normal constructor for given names
User::User(string name) :
    name(name), allow_push(true), connected(User::NOT_CONNECTED), temporal(false)
{
    ariba::Name n(name);
    id = n.toNodeId();

    // add standard directorys
    create_std_dirs();
}

// constructor for temporal (ID-only) users
User::User(const ariba::NodeID &id) :
    name("TEMPUSER"), id(id), allow_push(false), connected(User::NOT_CONNECTED), temporal(true)
{
}


User::User() :
    name("NOT_FOUND"), id(ariba::NodeID::UNSPECIFIED), allow_push(false), connected(User::NOT_CONNECTED), temporal(false)
{
}

User::User(ptree &pt) :
        name(pt.get<std::string>("name")), connected(User::NOT_CONNECTED), temporal(false)
{
    ariba::Name n(name);
    id = n.toNodeId();

    allow_push = pt.get("privileges.allow_push", false);
    // TODO allow_custody

    // file access
    try
    {
        for_each(ptree::value_type &v, pt.get_child("access"))
        {
            AccessPtr a(new Access_attributes(v.second));
            insert_access(a);
        }
    }
    catch (exception& e)
    {
        cout << "Warning !! No «access» subtree. user.xml corrupt?" << endl;
    }
}

User::~User()
{
//    if (flow_control)
//        delete flow_control;
}


void User::insert_access(AccessPtr a)
{
    access.insert(std::pair<string, AccessPtr>(a->get_path(), a));
}

void User::insert_access(string path, Access_attributes::ACCESS_RIGHTS privileges)
{
    AccessPtr a(new Access_attributes(path, privileges));
    insert_access(a);
}

/* updates or create an ACCESS */
void User::update_access(string path, Access_attributes::ACCESS_RIGHTS privileges)
{
    AccessMap::iterator it = access.find(path);

    // BRANCH: path not found
    if ( it == access.end() )
    {
        insert_access(path, privileges);
    }
    // BRANCH: path found -> update
    else
    {
        it->second->update_privileges(privileges);
    }
}


void User::create_std_dirs()
{
    string private_dir_name = get_masked_name();

    /* add standard directorys */
    // public
    if ( boost::filesystem::is_directory("public") )
    {
        insert_access("public", Access_attributes::PULL);
    }
    // if there is a private directory
    if ( boost::filesystem::is_directory("private") )
    {
        // give access to directory »name«
        string priv("private/" + private_dir_name);
        if ( boost::filesystem::is_directory(priv) ) // FIXME Sicherheitslücke. kein Zugriff auf bestehende Ordner geben.  -- Könnte hier andererseits erwartetes Verhalten sein.
        {
            insert_access(priv, Access_attributes::PULL);
        }
        // or create if necessary
        else if ( ! boost::filesystem::exists(priv) )
        {
            if ( boost::filesystem::create_directories(priv) )
                insert_access(priv, Access_attributes::PULL);
        }
    }
}


void User::register_for_link_up_event(LinkEstablishedInterface* listener)
{
    link_up_listeners.push_back(listener);
}


//** getter / setter **//
string User::get_name()
{
  return name;
}

// TODO noch mehr Sachen maskieren? String zwischenspeichern? maskierung eindeutig machen?
string User::get_masked_name()
{
    string n = name;
    size_t pos;
    while ( (pos = n.find("/")) != string::npos )
      n.replace(pos, 1, "_");

    return n;
}

/* sets name of a temporal user -> makes it to normal user, to be called from the userdatabase object */
bool User::set_name(string name)
{
    if ( temporal )
    {
        ariba::Name n(name);
        if ( id == n.toNodeId() )
        {
            // set name -> no longer temporal
            this->name = name;
            temporal = false;

            // add standard directorys
            create_std_dirs();

            return true;
        }
    }

    return false;
}

ariba::NodeID User::get_id()
{
  return id;
}


User::CONNECTION_STATUS User::get_connection_status()
{
    return connected;
}

bool User::is_connected()
{
    if ( connected == User::CONNECTED )
        return true;
    else
        return false;
}

bool User::connection_pending()
{
    if ( connected == User::CONNECTION_PENDING || connected == User::AUTHENTICATION_PENDING ||
            connected == User::SEND_ESTABLISHED || connected == User::RECEIVE_ESTABLISHED )
        return true;
    else
        return false;
}

bool User::is_disconnected()
{
    if ( connected == User::NOT_CONNECTED )
        return true;
    else
        return false;
}

void User::set_connected(User::CONNECTION_STATUS conn)
{
    this->connected = conn;

    if ( conn == User::CONNECTED && !link_up_listeners.empty() )
    {
        UserPtr u(self_reference);

        std::list<LinkEstablishedInterface*>::iterator it;
        for (it = link_up_listeners.begin(); it != link_up_listeners.end(); it++)
        {
            (*it)->link_up_callback(u);
            it = link_up_listeners.erase(it);
        }
    }
}


bool User::check_privilege_push()
{
    return allow_push;
}

// TODO privileg implementieren
bool User::check_privilege_custody()
{
    return true;
}

bool User::is_temporal()
{
    return temporal;
}

//Flow_control* User::get_flow_control()
//{
//    return flow_control;
//}
//
//void User::set_flow_control(Flow_control* fc)
//{
//    flow_control = fc;
//}

// TODO mit save() vereinen.. (ist auch von da rauskopiert..)
void User::list_shares(ptree &pt)
{
    ptree pt2;
    pt2.put("partner", name);
    
    AccessMap::iterator it;
    for ( it=access.begin(); it != access.end(); it++ )
    {
        ptree pt_a;
        it->second->save(pt_a);
        pt2.add_child("file", pt_a);
    }
    pt.add_child("access", pt2);
}

void User::list_accessible_files(ptree &pt, string dir)
{
    // BRANCH: top-level query -> list all direct accesses (directory depth: 1)
    if ( dir == "" )
    {
        pt.add("name", dir);
        
        AccessMap::iterator it;
        for ( it=access.begin(); it != access.end(); it++ )
        {
            // since we have privilege "none", check PULL privilege first
            if ( it->second->has_privilege(Access_attributes::PULL) )
            {
                // BRANCH: directory
                if ( boost::filesystem::is_directory(it->first) )
                {
                    ptree pt_d;
                    list_dir(it->first, pt_d);
                    pt.add_child("dir", pt_d);
                }
                // BRANCH: file
                else if ( boost::filesystem::is_regular(it->first) )
                {
                    ptree pt_f;
                    list_file(it->first, pt_f);
                    pt.add_child("file", pt_f);
                }
            }
        }
    }

    // BRANCH: directory query; access granted
    else if ( boost::filesystem::is_directory(dir) && file_has_privilege(dir, Access_attributes::PULL) )
    {
        list_dir(dir, pt);
    }

    // BRANCH: error, no such directory or access denied
    else
    {
//        out << "<no files for you>" << endl;
        ;
    }
}

void User::list_dir(string dir, ptree &pt)
{
//    ostringstream out;

//  out << "[dir] " << s << endl;
//    out << dir << "/" << endl;
    pt.add("name", dir);
    pt.add("size", -1);

    // FIXME directory Zugriff kann blockieren. Nehmt euch ein Beispiel am Sync-Zeugs.. ;-)
    directory_iterator end;
    for ( directory_iterator it(dir); it != end; ++it )
    {
        // BRANCH: directory
        if ( boost::filesystem::is_directory(it->path()) )
        {
            // count files in the directory
            int num = 0;
            for ( directory_iterator sub_it(it->path()); sub_it != end; ++sub_it )
                num++;

            // add data to ptree
            ptree pt_d;
            pt_d.add("name", it->path().string());
            pt_d.add("size", num);
            pt.add_child("subdir", pt_d);
        }
        // BRANCH: file
        else if ( boost::filesystem::is_regular(it->path()) )
        {
            ptree pt_f;
            list_file(it->path().string(), pt_f);
//            boost::uintmax_t size = boost::filesystem::file_size(it->path());
//
//            pt_f.add("name", it->path().string());
//            pt_f.add("size", size);

            pt.add_child("file", pt_f);
        }
    }

//    return out.str();
}

ariba::LinkID User::get_link_id()
{
    return link.link_id;
}

void User::update_link_info(link_info link)
{
    this->link = link;
}

//void User::set_link_id(ariba::LinkID link_id)
//{
//    this->link.link_id = link_id;
//    this->link.direct_link = false;
//    this->link.hops = 0;
//}

void User::list_file(string path, ptree &pt)
{
    // FIXME blockieren? siehe list_dir
    boost::uintmax_t size = boost::filesystem::file_size(path);

    pt.add("name", path);
    pt.add("size", size);
}


bool User::file_has_privilege(boost::filesystem::path dir, Access_attributes::ACCESS_RIGHTS priv)
{
    // if path/file doesn't exists -> ACCESS DENIED
    if ( ! boost::filesystem::exists(dir) )
        return false;

    // is accessible? (shortest prefix matching)
    boost::filesystem::path path_prefix;
    AccessMap::iterator res;
    bool granted = false;
    for (path::iterator it = dir.begin(); it != dir.end(); ++it)
    {
        // check for »..«-Attack
        if ( it->string() == ".." )
            throw runtime_error("SECURITY_EXCEPTION: »..«-Attack detected!! (Privilege-check for '" + dir.string() + "')");


        // * shortest prefix matching *
        // ---> if a matching is found, only the »..«-checking has to be done for the whole path
        if ( ! granted )
        {
            path_prefix /= *it;

            // search prefix
            res = access.find(path_prefix.string());
            cout << "-- XXX -- " << path_prefix.string() << "\n";  // XXX debug output

            // found prefix
            if ( res != access.end() )
            {
                // check access
                if ( res->second->has_privilege(priv) )
                    granted = true;
            }
        }
    }

    return granted;
}


void User::grant_file_permissions(string dir, Access_attributes::ACCESS_RIGHTS priv)
{
    if ( dir.at(dir.size() - 1) == '/' )
        dir = dir.substr(0, dir.size() - 1);

    update_access(dir, priv);
}


shared_ptr<ptree> User::save()
{
  shared_ptr<ptree> pt(new ptree());

  // main data
  pt->put("name", name);
  pt->put("id", id);

  // privileges
  shared_ptr<ptree> pt1(new ptree());
  pt1->put("allow_push", allow_push);
  pt->add_child("privileges", *pt1);

  // access
  shared_ptr<ptree> pt2(new ptree());
  AccessMap::iterator it;
  for ( it=access.begin(); it != access.end(); it++ )
  {
      ptree pt_a;
      it->second->save(pt_a);
      pt2->add_child("file", pt_a);
  }
  pt->add_child("access", *pt2);

  return pt;
}



void User::info(ptree &pt)
{
    pt.put("name", name);
    pt.put("id", id);
    pt.put("connected", connected);

    // link info
    if ( connected )
    {
        pt.put("direct-link", link.direct_link);
        pt.put("hops", link.hops);
    }
}

/** operators **/
bool User::operator!(void)
{
  return id == ariba::NodeID::UNSPECIFIED;
}
